// Web Worker for calculating file hash in chunks
// This avoids blocking the main thread and works in non-HTTPS environments

interface HashWorkerMessage {
  type: 'CALCULATE_HASH' | 'CANCEL';
  fileId: string;
  file: File;
  chunkSize?: number;
}

interface HashWorkerResponse {
  type: 'HASH_PROGRESS' | 'HASH_COMPLETE' | 'HASH_ERROR';
  fileId: string;
  progress?: number;
  hash?: string;
  error?: string;
}

// Simple SHA-256 implementation for non-HTTPS environments
class SimpleSHA256 {
  private h: number[] = [
    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
    0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19
  ];

  private k: number[] = [
    0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
    0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
    0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
    0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
    0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
    0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
    0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
    0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
  ];

  private rightRotate(value: number, amount: number): number {
    return (value >>> amount) | (value << (32 - amount));
  }

  private sha256(data: Uint8Array): string {
    const msg = new Uint8Array(data);
    const msgLength = msg.length;
    const bitLength = msgLength * 8;
    
    // Pre-processing: adding a single 1 bit
    const padded = new Uint8Array(((msgLength + 9) >> 6) << 4);
    padded.set(msg);
    padded[msgLength] = 0x80;
    
    // Append length as 64-bit big-endian integer
    const view = new DataView(padded.buffer);
    view.setUint32(padded.length - 4, Math.floor(bitLength / 0x100000000), false);
    view.setUint32(padded.length - 8, bitLength, false);
    
    // Process the message in successive 512-bit chunks
    for (let chunk = 0; chunk < padded.length; chunk += 64) {
      const w = new Array(64);
      
      // Break chunk into sixteen 32-bit big-endian words
      for (let i = 0; i < 16; i++) {
        w[i] = view.getUint32(chunk + i * 4, false);
      }
      
      // Extend the sixteen 32-bit words into sixty-four 32-bit words
      for (let i = 16; i < 64; i++) {
        const s0 = this.rightRotate(w[i - 15], 7) ^ this.rightRotate(w[i - 15], 18) ^ (w[i - 15] >>> 3);
        const s1 = this.rightRotate(w[i - 2], 17) ^ this.rightRotate(w[i - 2], 19) ^ (w[i - 2] >>> 10);
        w[i] = (w[i - 16] + s0 + w[i - 7] + s1) >>> 0;
      }
      
      // Initialize hash value for this chunk
      let [a, b, c, d, e, f, g, h] = this.h;
      
      // Main loop
      for (let i = 0; i < 64; i++) {
        const s1 = this.rightRotate(e, 6) ^ this.rightRotate(e, 11) ^ this.rightRotate(e, 25);
        const ch = (e & f) ^ (~e & g);
        const temp1 = (h + s1 + ch + this.k[i] + w[i]) >>> 0;
        const s0 = this.rightRotate(a, 2) ^ this.rightRotate(a, 13) ^ this.rightRotate(a, 22);
        const maj = (a & b) ^ (a & c) ^ (b & c);
        const temp2 = (s0 + maj) >>> 0;
        
        h = g;
        g = f;
        f = e;
        e = (d + temp1) >>> 0;
        d = c;
        c = b;
        b = a;
        a = (temp1 + temp2) >>> 0;
      }
      
      // Add this chunk's hash to result so far
      this.h[0] = (this.h[0] + a) >>> 0;
      this.h[1] = (this.h[1] + b) >>> 0;
      this.h[2] = (this.h[2] + c) >>> 0;
      this.h[3] = (this.h[3] + d) >>> 0;
      this.h[4] = (this.h[4] + e) >>> 0;
      this.h[5] = (this.h[5] + f) >>> 0;
      this.h[6] = (this.h[6] + g) >>> 0;
      this.h[7] = (this.h[7] + h) >>> 0;
    }
    
    // Produce the final hash value as a 256-bit number
    return this.h.map(h => h.toString(16).padStart(8, '0')).join('');
  }

  public async hash(data: Uint8Array): Promise<string> {
    return this.sha256(data);
  }
}

// Use crypto.subtle if available (HTTPS), otherwise use our implementation
async function calculateHash(data: Uint8Array): Promise<string> {
  if (typeof crypto !== 'undefined' && crypto.subtle) {
    try {
      const hashBuffer = await crypto.subtle.digest('SHA-256', data);
      const hashArray = Array.from(new Uint8Array(hashBuffer));
      return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    } catch (error) {
      console.warn('crypto.subtle not available, falling back to simple implementation');
    }
  }
  
  // Fallback to simple implementation
  const sha256 = new SimpleSHA256();
  return await sha256.hash(data);
}

// Calculate hash in chunks to avoid blocking
async function calculateFileHashInChunks(
  file: File, 
  chunkSize: number = 1024 * 1024, // 1MB chunks
  onProgress?: (progress: number) => void
): Promise<string> {
  const totalChunks = Math.ceil(file.size / chunkSize);
  let processedChunks = 0;
  
  // Use a simple hash combining approach for large files
  // This is not cryptographically secure but good enough for file deduplication
  let combinedHash = '';
  
  for (let i = 0; i < totalChunks; i++) {
    const start = i * chunkSize;
    const end = Math.min(start + chunkSize, file.size);
    const chunk = file.slice(start, end);
    
    const arrayBuffer = await chunk.arrayBuffer();
    const uint8Array = new Uint8Array(arrayBuffer);
    const chunkHash = await calculateHash(uint8Array);
    
    // Combine hashes using a simple method
    combinedHash = combinedHash + chunkHash;
    
    processedChunks++;
    const progress = (processedChunks / totalChunks) * 100;
    onProgress?.(progress);
    
    // Yield control to prevent blocking
    await new Promise(resolve => setTimeout(resolve, 0));
  }
  
  // Hash the combined result to get final hash
  const finalHash = await calculateHash(new TextEncoder().encode(combinedHash));
  return finalHash;
}

// Handle messages from main thread
self.onmessage = async (event: MessageEvent<HashWorkerMessage>) => {
  const { type, fileId, file, chunkSize = 1024 * 1024 } = event.data;
  
  if (type === 'CALCULATE_HASH') {
    try {
      const hash = await calculateFileHashInChunks(
        file, 
        chunkSize,
        (progress) => {
          const response: HashWorkerResponse = {
            type: 'HASH_PROGRESS',
            fileId,
            progress
          };
          self.postMessage(response);
        }
      );
      
      const response: HashWorkerResponse = {
        type: 'HASH_COMPLETE',
        fileId,
        hash
      };
      self.postMessage(response);
    } catch (error) {
      const response: HashWorkerResponse = {
        type: 'HASH_ERROR',
        fileId,
        error: error instanceof Error ? error.message : 'Unknown error'
      };
      self.postMessage(response);
    }
  }
};
